﻿' ----------------------------------------------------------------------------------
' Microsoft Developer & Platform Evangelism
' 
' Copyright (c) Microsoft Corporation. All rights reserved.
' 
' THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
' EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
' OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ----------------------------------------------------------------------------------
' The example companies, organizations, products, domain names,
' e-mail addresses, logos, people, places, and events depicted
' herein are fictitious.  No association with any real company,
' organization, product, domain name, email address, logo, person,
' places, or events is intended or should be inferred.
' ----------------------------------------------------------------------------------

Imports WindowsPhone.Recipes.Push.Messages
Imports Microsoft.WindowsAzure.StorageClient
Imports Microsoft.WindowsAzure
Imports Microsoft.Samples.WindowsPhoneCloud.Web.UserAccountWrappers
Imports Microsoft.Samples.WindowsPhoneCloud.Web.Models
Imports Microsoft.Samples.WindowsPhoneCloud.Web.Infrastructure
Imports System.ServiceModel.Web
Imports System.ServiceModel.Activation
Imports System.ServiceModel
Imports System.Net
Imports System.Globalization
#If ACS Then
Imports Microsoft.IdentityModel.Claims
#End If
Namespace Services
    <ServiceBehavior(IncludeExceptionDetailInFaults:=False), AspNetCompatibilityRequirements(RequirementsMode:=AspNetCompatibilityRequirementsMode.Required)>
    Public Class SamplePushUserRegistrationService
        Implements ISamplePushUserRegistrationService
        Private ReadOnly context As HttpContextBase
        Private ReadOnly pushUserEndpointsRepository As IPushUserEndpointsRepository
        Private ReadOnly cloudQueueClient As CloudQueueClient
        Private ReadOnly webOperationContext As WebOperationContext

#If ACS Then
		Public Sub New()
			Me.New(New HttpContextWrapper(HttpContext.Current), WebOperationContext.Current, New UserTablesServiceContext(), Nothing)
		End Sub

		<CLSCompliant(False)>
		Public Sub New(ByVal context As HttpContextBase, ByVal webOperationContext As WebOperationContext, ByVal pushUserEndpointsRepository As IPushUserEndpointsRepository, ByVal cloudQueueClient As CloudQueueClient)
			If (context Is Nothing) AndAlso (HttpContext.Current Is Nothing) Then
				Throw New ArgumentNullException("context", "Context cannot be null if not running on a Web context.")
			End If

			If pushUserEndpointsRepository Is Nothing Then
				Throw New ArgumentNullException("pushUserEndpointsRepository", "PushUserEndpoints repository cannot be null.")
			End If

			If (cloudQueueClient Is Nothing) AndAlso (GetStorageAccountFromConfigurationSetting() Is Nothing) Then
				Throw New ArgumentNullException("cloudQueueClient", "Cloud Queue Client cannot be null if no configuration is loaded.")
			End If

			Me.cloudQueueClient = If(cloudQueueClient, GetStorageAccountFromConfigurationSetting().CreateCloudQueueClient())
			Me.webOperationContext = webOperationContext
			Me.context = context
			Me.pushUserEndpointsRepository = pushUserEndpointsRepository
		End Sub

		Private ReadOnly Property UserId() As String
			Get
                Dim identity = TryCast(HttpContext.Current.User.Identity, IClaimsIdentity)
                Return identity.Claims.Single(Function(c) c.ClaimType = ClaimTypes.NameIdentifier).Value
			End Get
		End Property
#Else
        Private ReadOnly formsAuth As IFormsAuthentication
        Private ReadOnly membershipService As IMembershipService

        Public Sub New()
            Me.New(New HttpContextWrapper(HttpContext.Current), webOperationContext.Current, New FormsAuthenticationService(), New AccountMembershipService(), New UserTablesServiceContext(), Nothing)
        End Sub

        <CLSCompliant(False)>
        Public Sub New(ByVal context As HttpContextBase, ByVal webOperationContext As WebOperationContext, ByVal formsAuth As IFormsAuthentication, ByVal membershipService As IMembershipService, ByVal pushUserEndpointsRepository As IPushUserEndpointsRepository, ByVal cloudQueueClient As CloudQueueClient)
            If (context Is Nothing) AndAlso (HttpContext.Current Is Nothing) Then
                Throw New ArgumentNullException("context", "Context cannot be null if not running on a Web context.")
            End If

            If pushUserEndpointsRepository Is Nothing Then
                Throw New ArgumentNullException("pushUserEndpointsRepository", "PushUserEndpoints repository cannot be null.")
            End If

            If formsAuth Is Nothing Then
                Throw New ArgumentNullException("formsAuth", "Forms Authentication service cannot be null.")
            End If

            If membershipService Is Nothing Then
                Throw New ArgumentNullException("membershipService", "Membership service cannot be null.")
            End If

            If (cloudQueueClient Is Nothing) AndAlso (GetStorageAccountFromConfigurationSetting() Is Nothing) Then
                Throw New ArgumentNullException("cloudQueueClient", "Cloud Queue Client cannot be null if no configuration is loaded.")
            End If

            Me.cloudQueueClient = If(cloudQueueClient, GetStorageAccountFromConfigurationSetting().CreateCloudQueueClient())
            Me.webOperationContext = webOperationContext
            Me.context = context
            Me.pushUserEndpointsRepository = pushUserEndpointsRepository
            Me.formsAuth = formsAuth
            Me.membershipService = membershipService
        End Sub

        Private ReadOnly Property UserId() As String
            Get
                Dim ticketValue As String = Nothing

                Dim cookie = Me.context.Request.Cookies(Me.formsAuth.FormsCookieName)
                If cookie IsNot Nothing Then
                    ' From cookie.
                    ticketValue = cookie.Value
                ElseIf Me.context.Request.Headers("AuthToken") IsNot Nothing Then
                    ' From HTTP header.
                    ticketValue = Me.context.Request.Headers("AuthToken")
                End If

                If Not String.IsNullOrEmpty(ticketValue) Then
                    Dim ticket As FormsAuthenticationTicket

                    Try
                        ticket = Me.formsAuth.Decrypt(ticketValue)
                    Catch
                        Throw New WebFaultException(Of String)("The authorization ticket cannot be decrypted.", HttpStatusCode.Unauthorized)
                    End Try

                    If ticket IsNot Nothing Then
                        Return Me.membershipService.GetUser(New FormsIdentity(ticket).Name).ProviderUserKey.ToString()
                    Else
                        Throw New WebFaultException(Of String)("The authorization token is no longer valid.", HttpStatusCode.Unauthorized)
                    End If
                Else
                    Throw New WebFaultException(Of String)("Resource not found.", HttpStatusCode.NotFound)
                End If
            End Get
        End Property
#End If

        Public Function Register(ByVal pushUserRegister As PushUserServiceRequest) As String Implements ISamplePushUserRegistrationService.Register
            ' Authenticate.
            Dim userId = Me.UserId

            Try
                Dim pushUserEndpoint = Me.pushUserEndpointsRepository.GetPushUserByApplicationAndDevice(pushUserRegister.ApplicationId, pushUserRegister.DeviceId)
                If pushUserEndpoint Is Nothing Then
                    Dim newPushUserEndPoint = New PushUserEndpoint(pushUserRegister.ApplicationId, pushUserRegister.DeviceId) With {.ChannelUri = pushUserRegister.ChannelUri.ToString(), .UserId = userId}
                    Me.pushUserEndpointsRepository.AddPushUserEndpoint(newPushUserEndPoint)
                Else
                    ' If the user did not change the channel URI, then, there is nothing to update, otherwise, set the new connection status
                    If (Not pushUserEndpoint.ChannelUri.Equals(pushUserRegister.ChannelUri.ToString())) OrElse (Not pushUserEndpoint.UserId.Equals(userId)) Then
                        ' Update che channelUri for the UserEndpoint and reset status fields.
                        pushUserEndpoint.ChannelUri = pushUserRegister.ChannelUri.ToString()
                        pushUserEndpoint.UserId = userId
                    End If

                    Me.pushUserEndpointsRepository.UpdatePushUserEndpoint(pushUserEndpoint)
                End If
            Catch exception As Exception
                Throw New WebFaultException(Of String)(String.Format(CultureInfo.InvariantCulture, "There was an error registering the Push Notification Endpoint: {0}", exception.Message), HttpStatusCode.InternalServerError)
            End Try

            Return "Success"
        End Function

        Public Function Unregister(ByVal pushUserUnregister As PushUserServiceRequest) As String Implements ISamplePushUserRegistrationService.Unregister
            ' Authenticate.
            Dim userId = Me.UserId

            Try
                Me.pushUserEndpointsRepository.RemovePushUserEndpoint(New PushUserEndpoint(pushUserUnregister.ApplicationId, pushUserUnregister.DeviceId) With {.UserId = userId})
            Catch exception As Exception
                Throw New WebFaultException(Of String)(String.Format(CultureInfo.InvariantCulture, "There was an error unregistering the Push Notification Endpoint: {0}", exception.Message), HttpStatusCode.InternalServerError)
            End Try

            Return "Success"
        End Function

        Public Function GetUpdates(ByVal applicationId As String, ByVal deviceId As String) As String() Implements ISamplePushUserRegistrationService.GetUpdates
            ' Authenticate.
            Dim userId = Me.UserId

            Me.webOperationContext.OutgoingResponse.Headers.Add("Cache-Control", "no-cache")
            Dim userEndpoint = Me.pushUserEndpointsRepository.GetPushUserByApplicationAndDevice(applicationId, deviceId)
            Try
                Dim queueName = GetQueueName(applicationId, deviceId, userEndpoint.UserId)
                Dim queue = Me.cloudQueueClient.GetQueueReference(queueName)
                Dim messages = New List(Of String)()
                If queue.Exists() Then
                    Dim message = queue.GetMessage()
                    Do While message IsNot Nothing
                        messages.Add(message.AsString)
                        queue.DeleteMessage(message)
                        message = queue.GetMessage()
                    Loop
                End If

                Me.ResetTileNotificationCount(applicationId, deviceId)

                Return messages.ToArray()
            Catch exception As Exception
                Throw New WebFaultException(Of String)(String.Format(CultureInfo.InvariantCulture, "There was an error getting the push notification updates: {0}", exception.Message), HttpStatusCode.InternalServerError)
            End Try
        End Function

        Private Shared Function GetQueueName(ByVal applicationId As String, ByVal deviceId As String, ByVal userId As String) As String
            Dim uniqueName = String.Concat(applicationId, deviceId, userId)

            Return String.Concat("notification", uniqueName.GetHashCode())
        End Function

        Private Shared Function GetStorageAccountFromConfigurationSetting() As CloudStorageAccount
            Dim account As CloudStorageAccount = Nothing

            Try
                account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString")
            Catch e1 As InvalidOperationException
                account = Nothing
            End Try

            Return account
        End Function

		Private Sub ResetTileNotificationCount(ByVal applicationId As String, ByVal deviceId As String)
            Dim pushUserEndpoint = Me.pushUserEndpointsRepository.GetPushUserByApplicationAndDevice(applicationId, deviceId)

            pushUserEndpoint.TileCount = 0

            Dim tile = New TilePushNotificationMessage With {.SendPriority = MessageSendPriority.High, .Count = pushUserEndpoint.TileCount}

            ' Send a new tile notification message to reset the count in the phone application.
            tile.SendAndHandleErrors(New Uri(pushUserEndpoint.ChannelUri))

            ' Save the updated count.
            Me.pushUserEndpointsRepository.UpdatePushUserEndpoint(pushUserEndpoint)
        End Sub
    End Class
End Namespace